#include "ce_core.h"
#include <stdatomic.h>

#define ce_lfqueue_empty(lfq)                   \
    ((lfq)->qhead == (lfq)->qtail)

#define ce_lfqueue_full(lfq)                    \
    (((lfq)->qtail + 1) % (lfq)->qsize == (lfq)->qhead)

#define ce_lfqueue_next(lfq, idx)               \
    ((idx + 1) % (lfq)->qsize)

ce_lfqueue_t *
ce_lfqueue_create(size_t size) {
    ce_lfqueue_t       *lfq;

    lfq = malloc(sizeof(ce_lfqueue_t));

    lfq->qsize = size;
    lfq->qhead = 0;
    lfq->qtail = 0;

    ce_spinlock_init(&lfq->lock);
    lfq->ringbuf = calloc(size, sizeof(void*));

    return lfq;
}

void
ce_lfqueue_destroy(ce_lfqueue_t *lfq) {
    free(lfq->ringbuf);
    ce_spinlock_destroy(&lfq->lock);
    free(lfq);
}
#if 0
int
ce_lfqueue_push(ce_lfqueue_t *lfq, void *data) {
    size_t              tail, idx;

    ce_spinlock_lock(&lfq->lock);
    if (ce_lfqueue_full(lfq)) {
        ce_spinlock_unlock(&lfq->lock);
        return 0;
    }

    lfq->ringbuf[lfq->qtail] = data;
    lfq->qtail = ce_lfqueue_next(lfq, lfq->qtail);

    ce_spinlock_unlock(&lfq->lock);

    return 1;
}

void *
ce_lfqueue_pop(ce_lfqueue_t *lfq) {
    size_t              head, idx;
    void               *data;

    ce_spinlock_lock(&lfq->lock);
    if (ce_lfqueue_empty(lfq)) {
        ce_spinlock_unlock(&lfq->lock);
        return NULL;
    }

    data = lfq->ringbuf[lfq->qhead];
    lfq->qhead = ce_lfqueue_next(lfq, lfq->qhead);

    ce_spinlock_unlock(&lfq->lock);

    return data;
}
#else
int
ce_lfqueue_push(ce_lfqueue_t *lfq, void *data) {
    size_t              head, tail, idx;

    if (data == NULL) {
        return 0;
    }

    head = lfq->qhead;
    tail = lfq->qtail;
    idx = ce_lfqueue_next(lfq, tail);

    if (head == idx) {
        return 0;
    }

    if (!ce_atomic_cmp_set(&lfq->ringbuf[tail], NULL, data)) {
        return 0;
    }

    if (!ce_atomic_cmp_set(&lfq->qtail, tail, idx)) {
        ce_atomic_cmp_set(&lfq->ringbuf[tail], data, NULL);
        return 0;
    }

    return 1;
}

void *
ce_lfqueue_pop(ce_lfqueue_t *lfq) {
    size_t              head, tail, idx;
    void               *data;

    head = lfq->qhead;
    tail = lfq->qtail;
    idx = ce_lfqueue_next(lfq, head);
    data = lfq->ringbuf[head];

    if (head == tail) {
        return NULL;
    }

    if (data == NULL) {
        return NULL;
    }

    if (!ce_atomic_cmp_set(&lfq->ringbuf[head], data, NULL)) {
        return NULL;
    }

    if (!ce_atomic_cmp_set(&lfq->qhead, head, idx)) {
        ce_atomic_cmp_set(&lfq->ringbuf[head], NULL, data);
        return NULL;
    }

    return data;
}
#endif
